/**
 * Created by lvlq on 16/3/29.
 */
var QueryString = {};

QueryString.stringify = QueryString.encode = function (obj, sep, eq, options) {
    sep = sep || '&';
    eq = eq || '=';

    var encode = QueryString.escape;

    if (obj !== null && typeof obj === 'object') {
        var keys = Object.keys(obj);
        var len = keys.length;
        var flast = len - 1;
        var fields = '';
        for (var i = 0; i < len; ++i) {
            var k = keys[i];
            var v = obj[k];
            var ks = encode(stringifyPrimitive(k)) + eq;

            if (Array.isArray(v)) {
                var vlen = v.length;
                var vlast = vlen - 1;
                for (var j = 0; j < vlen; ++j) {
                    fields += ks + encode(stringifyPrimitive(v[j]));
                    if (j < vlast)
                        fields += sep;
                }
                if (vlen && i < flast)
                    fields += sep;
            } else {
                fields += ks + encode(stringifyPrimitive(v));
                if (i < flast)
                    fields += sep;
            }
        }
        return fields;
    }
    return '';
};

QueryString.escape = function (str) {
    // replaces encodeURIComponent
    // http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4
    str = '' + str;
    var len = str.length;
    var out = '';
    var i, c;

    if (len === 0)
        return str;

    for (i = 0; i < len; ++i) {
        c = str.charCodeAt(i);

        // These characters do not need escaping (in order):
        // ! - . _ ~
        // ' ( ) *
        // digits
        // alpha (uppercase)
        // alpha (lowercase)
        if (c === 0x21 || c === 0x2D || c === 0x2E || c === 0x5F || c === 0x7E ||
            (c >= 0x27 && c <= 0x2A) ||
            (c >= 0x30 && c <= 0x39) ||
            (c >= 0x41 && c <= 0x5A) ||
            (c >= 0x61 && c <= 0x7A)) {
            out += str[i];
            continue;
        }

        // Other ASCII characters
        if (c < 0x80) {
            out += hexTable[c];
            continue;
        }

        // Multi-byte characters ...
        if (c < 0x800) {
            out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)];
            continue;
        }
        if (c < 0xD800 || c >= 0xE000) {
            out += hexTable[0xE0 | (c >> 12)] +
                hexTable[0x80 | ((c >> 6) & 0x3F)] +
                hexTable[0x80 | (c & 0x3F)];
            continue;
        }
        // Surrogate pair
        ++i;
        c = 0x10000 + (((c & 0x3FF) << 10) | (str.charCodeAt(i) & 0x3FF));
        out += hexTable[0xF0 | (c >> 18)] +
            hexTable[0x80 | ((c >> 12) & 0x3F)] +
            hexTable[0x80 | ((c >> 6) & 0x3F)] +
            hexTable[0x80 | (c & 0x3F)];
    }
    return out;
};

var hexTable = new Array(256);
for (var i = 0; i < 256; ++i)
    hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();

var stringifyPrimitive = function (v) {
    if (typeof v === 'string')
        return v;
    if (typeof v === 'number' && isFinite(v))
        return '' + v;
    if (typeof v === 'boolean')
        return v ? 'true' : 'false';
    return '';
};

QueryString.parse = function (qs, sep, eq, options) {
    sep = sep || '&';
    eq = eq || '=';
    var eqLen = eq.length;
    var obj = {};

    if (typeof qs !== 'string' || qs.length === 0) {
        return obj;
    }

    qs = qs.split(sep);

    var maxKeys = 1000;
    if (options && typeof options.maxKeys === 'number') {
        maxKeys = options.maxKeys;
    }

    var len = qs.length;
    // maxKeys <= 0 means that we should not limit keys count
    if (maxKeys > 0 && len > maxKeys) {
        len = maxKeys;
    }

    var decode = QueryString.unescape;
    if (options && typeof options.decodeURIComponent === 'function') {
        decode = options.decodeURIComponent;
    }

    var keys = [];
    for (var i = 0; i < len; i++) {
        // replacePlus() is used instead of a regexp because it is ~15-30% faster
        // with v8 4.7
        var x = replacePlus(qs[i]);
        var idx = x.indexOf(eq);
        var k, v;

        if (idx >= 0) {
            k = decodeStr(x.substring(0, idx));
            v = decodeStr(x.substring(idx + eqLen));
        } else {
            k = decodeStr(x);
            v = '';
        }

        // Use a key array lookup instead of using hasOwnProperty(), which is slower
        if (keys.indexOf(k) === -1) {
            obj[k] = v;
            keys.push(k);
        } else if (obj[k] instanceof Array) {
            // `instanceof Array` is used instead of Array.isArray() because it is
            // ~15-20% faster with v8 4.7 and is safe to use because we are using it
            // with values being created within this function
            obj[k].push(v);
        } else {
            obj[k] = [obj[k], v];
        }
    }

    return obj;
};


function replacePlus(str) {
    var ret = '';
    var start = 0;
    var i = -1;
    while ((i = str.indexOf('+', i + 1)) !== -1) {
        ret += str.slice(start, i);
        ret += '%20';
        start = i + 1;
    }
    if (start === 0)
        return str;
    if (start < str.length)
        ret += str.slice(start);
    return ret;
}


function decodeStr(s) {
    return unescape(s);
}
